﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BlobNet.Base.Collections;
using BlobNet.Base.Common;
using BlobNet.Base.Enums;
using BlobNet.Base.Interfaces;

namespace BlobNet.Base.Storage
{
    //TODO: MAKE THIS PLUGABLE
    [Serializable]
    internal class MemoryStorage : StorageBase, IStorageManager
    {
        #region Attributes
        private Dictionary<string, object> _cache;

        #endregion Attributes

        #region Properties
        public Dictionary<string, object> Cache
        {
            get { return _cache; }
            protected set
            {
                if (value == null)
                    throw new NullReferenceException();
                _cache = value;
            }
        }

        public override bool HasStatusValue(string propertyName)
        {
            return Cache.ContainsKey(propertyName);
        }
        #endregion


        #region Constructors

        public MemoryStorage(Guid entityId, BlobDomain blobDomain)
            : this(entityId, blobDomain, new Dictionary<string, object>())
        {
        }

        protected MemoryStorage(Guid entityId, BlobDomain blobDomain, Dictionary<string, object> cache)
            : base(entityId, blobDomain)
        {
            if (cache == null) throw new ArgumentNullException("cache");
            _cache = cache;
        }



        #endregion Constructors

        #region Public Methods

        public override object SetStatusValue<TPropertyType>(string propertyName, TPropertyType value)
        {
            object current = value;
            PropertyValueType propertyType = TypesHelper.GetPropertyValueType(value);

            //if (propertyType == PropertyValueType.WrappedObject)
            //{
            //    if (value != null)
            //        current = WrapObject(value);
            //}

            if (propertyType == PropertyValueType.NotSupported)
            {
                throw new NotSupportedException();
            }

            if (!Cache.ContainsKey(propertyName))
            {
                Cache.Add(propertyName, current);
            }
            else
            {
                Cache[propertyName] = current;
            }

            //not the returns the same reference if is not 'new collection' 
            return current;
        }


        public override TPropertyType GetStatusValue<TPropertyType>(string propertyName)
        {
            TPropertyType result;

            if (!Cache.ContainsKey(propertyName))
                //throw new InvalidOperationException("The property value must be set before it's first access");
                result = default(TPropertyType);
            else
                result = (TPropertyType) Cache[propertyName];


            return result;
        }


        #region Implementation of IStatusStorage
        public override void ClearData()
        {
            Cache.Clear();
        }

        public override void FillFrom(IStorageManager currentStorage)
        {
            var registeredProperties= currentStorage.GetRegisteredProperties();

            foreach (string key in registeredProperties)
            {
                SetStatusValue(key,
                               ((PersistentStorage) currentStorage).GetStatusValue<object>(key));
            }
        }

        public override bool CanDettach()
        {
            return true;
        }

        public IList<string> GetRegisteredProperties()
        {
            return new List<string>(Cache.Keys);
        }

        public List<TItemType> GetItems<TItemType>() where TItemType : BlobPart  //TODO: IMPROVE THIS
        {
            var result=new List<TItemType>();

            result.AddRange(ExploreCache<TItemType>(this.Cache));

            return result;
        }

      

        private IEnumerable<TItemType> ExploreCache<TItemType>(Dictionary<string, object> target) where TItemType : BlobPart
        {
            List<TItemType> result=new List<TItemType>();

            //explore blobparts
           var toExplore = target.Where(x=>x.Value is BlobPart).Select(x=>x.Value).Cast<BlobPart>().ToList();
            foreach (var blobPart in toExplore)
            {
                MemoryStorage storage = (MemoryStorage)blobPart.CurrentStorage;
                result.AddRange(ExploreCache<TItemType>(storage.Cache));
            }
            //add item if collections
            IEnumerable<object> enumerable = target.Where(x => x.Value is IEnumerable<BlobPart>).Select(x => x.Value);
            foreach (var lst in enumerable)
            {
                if(lst is IEnumerable<TItemType>)
                    result.AddRange((IEnumerable<TItemType>)lst);
                else
                {

                    var x = lst as IEnumerable;
                    foreach (var o in x)
                    {
                        var currentStorage = (MemoryStorage)((BlobPart)o).CurrentStorage;
                        result.AddRange(ExploreCache<TItemType>(currentStorage.Cache));
                        
                    }
                }
            }

            //current instance result
            result.AddRange(target.Where(x => x.Value is TItemType).Select(x=>x.Value).Cast<TItemType>());


            return result;
        }

        public override IList<IPersisteable> GetVariantSourcesItems()
        {
            //TODO: BIiiiiiG botch

            List<IPersisteable> result =
                Cache.Values.Where(
                    x =>
                    x != null &&
                    x.GetType().GetInterface("BlobNet.Base.Interfaces.IVariantStatusSource") != null).Select(
                        x => (IPersisteable)x).ToList();
            return result;
        }

        #endregion

        #endregion Public Methods

        #region Not Public Methods

        #endregion Not Public Methods

        #region Implementation of IDeserializationCallback

        public virtual void OnDeserialization(object sender)
        {
            //do nothing as It keeps de dictionary
        }

        #endregion
    }
}